package com.eclipse.training.extensions.editors;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.viewers.DecoratingLabelProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.forms.editor.FormPage;
import org.eclipse.ui.forms.events.ExpansionAdapter;
import org.eclipse.ui.forms.events.ExpansionEvent;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.forms.widgets.TableWrapData;
import org.eclipse.ui.forms.widgets.TableWrapLayout;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertySheetPageContributor;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
import org.jdom2.Document;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.xml.sax.SAXException;

import com.eclipse.training.extensions.Activator;
import com.eclipse.training.extensions.ExtensionParser;
import com.eclipse.training.extensions.IMyExtension;
import com.eclipse.training.extensions.LogManager;
import com.eclipse.training.extensions.actions.AddAction;
import com.eclipse.training.extensions.actions.RemAction;
import com.eclipse.training.extensions.properties.TrainingPropertyPage;
import com.eclipse.training.extensions.ui.ModelContentProvider;
import com.eclipse.training.extensions.ui.ModelDecoratorLabelProvider;
import com.eclipse.training.extensions.ui.ModelLabelProvider;
import com.eclipse.training.models.ModelContainer;
import com.eclipse.training.models.ModelElement;
import com.eclipse.training.models.ModelProvider;

public class TrainingPageEditor extends FormEditor implements
		IResourceChangeListener, ITabbedPropertySheetPageContributor {

	private TextEditor editor;
	private ModelContainer model;
	private TreeViewer viewer;
	private AddAction addCAction;
	private RemAction remCAction;
	private AddAction addEAction;
	private RemAction remEAction;
	private boolean dirty = false;
	private boolean changed = false;
	private boolean deleted = false;
	private Section section;

	/**
	 * The Delta Visitor class
	 * 
	 */
	class DeltaPrinter implements IResourceDeltaVisitor {
		public boolean visit(IResourceDelta delta) {
			final FileEditorInput input = (FileEditorInput) getEditorInput();
			IResource res = delta.getResource();

			switch (delta.getKind()) {
			case IResourceDelta.SYNC:
				break;
			case IResourceDelta.REMOVED:
				/*
				 * Close the editor
				 */
				if (res.getFullPath().equals(input.getFile().getFullPath())) {
					Display.getDefault().syncExec(new Runnable() {
						public void run() {
							getSite().getPage().closeEditor(
									TrainingPageEditor.this, isDirty());
						}
					});
				}
				break;
			case IResourceDelta.CHANGED:
				System.out.println(res);
				/*
				 * Set the editor to be updated
				 */
				if (res.getFullPath().equals(input.getFile().getFullPath()))
					changed = true;
				break;
			}
			return true; // visit the children
		}
	}

	/**
	 * Construct the editor instance and add the resource change listener
	 */
	public TrainingPageEditor() {
		super();
		ResourcesPlugin.getWorkspace().addResourceChangeListener(
				this,
				IResourceChangeEvent.PRE_CLOSE
						| IResourceChangeEvent.PRE_DELETE
						| IResourceChangeEvent.POST_CHANGE);
	}

	/**
	 * Initialize the editor content and build the model
	 */
	public void init(IEditorSite site, IEditorInput editorInput)
			throws PartInitException {
		if (!(editorInput instanceof IFileEditorInput))
			throw new PartInitException(
					"Invalid Input: Must be IFileEditorInput"); //$NON-NLS-1$

		try {
			model = ModelProvider.update(editorInput);
			executeExtensionProcess(editorInput);
		} catch (CoreException | SAXException | IOException e) {
			throw new PartInitException(Messages.TrainingPageEditor_1, e);
		}
		super.init(site, editorInput);
	}

	private void executeExtensionProcess(IEditorInput editorInput) {
		FileEditorInput fileEditorInput = (FileEditorInput) editorInput;
		IFile file = fileEditorInput.getFile();

		ArrayList<IMyExtension> executables = ExtensionParser.getInstance()
				.getExecutables();

		LogManager.getLogger().logInfo(Messages.TrainingPageEditor_2);
		for (IMyExtension executable : executables)
			executable.doSomeWork(file);
	}

	/**
	 * Add the pages
	 */
	@Override
	protected void addPages() {
		createPage0();
		createPage1();
	}

	private void createPage1() {
		try {
			editor = new TextEditor();
			int index = addPage(editor, getEditorInput());
			setPageText(index, editor.getTitle());
		} catch (PartInitException e) {
			LogManager.getLogger().logError(e);
		}
	}

	private void createPage0() {
		FormPage page = new FormPage(this, "id", "Page") { //$NON-NLS-1$ //$NON-NLS-2$
			@Override
			protected void createFormContent(IManagedForm managedForm) {
				FormToolkit toolkit = managedForm.getToolkit();

				final ScrolledForm form = managedForm.getForm();
				form.getBody().setLayout(new TableWrapLayout());
				form.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB,
						TableWrapData.FILL_GRAB));

				toolkit.decorateFormHeading(form.getForm());
				String owner = null;
				try {
					owner = ((FileEditorInput) getEditorInput())
							.getFile()
							.getPersistentProperty(
									new QualifiedName("", //$NON-NLS-1$
											TrainingPropertyPage.OWNER_PROPERTY));
				} catch (CoreException e) {
					LogManager.getLogger().logError(e);
				}

				if (owner != null && owner.length() > 0)
					form.setText(Messages.TrainingPageEditor_6 + owner);
				else
					form.setText(Messages.TrainingPageEditor_7);

				form.setImage(Activator.getDefault().getImage(
						Activator.IMAGE_XML));

				section = toolkit.createSection(form.getBody(),
						Section.DESCRIPTION | Section.TITLE_BAR
								| Section.EXPANDED | Section.TWISTIE);

				section.setText(Messages.TrainingPageEditor_8);
				section.setDescription(Messages.TrainingPageEditor_9);

				section.setLayout(new TableWrapLayout());
				section.setLayoutData(new TableWrapData(
						TableWrapData.FILL_GRAB, TableWrapData.FILL_GRAB));

				section.addExpansionListener(new ExpansionAdapter() {
					public void expansionStateChanged(ExpansionEvent e) {
						form.reflow(true);
					}
				});

				Composite client = toolkit.createComposite(section, SWT.NONE);

				client.setLayout(new TableWrapLayout());
				client.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB,
						TableWrapData.FILL_GRAB));

				section.setClient(client);

				viewer = new TreeViewer(client, SWT.SINGLE | SWT.H_SCROLL
						| SWT.V_SCROLL);
				viewer.setContentProvider(new ModelContentProvider());

				// viewer.setLabelProvider(new ModelLabelProvider());

				/*
				 * Use the preference color
				 */
				viewer.setLabelProvider(new DecoratingLabelProvider(
						new ModelLabelProvider(),
						new ModelDecoratorLabelProvider()));

				viewer.setAutoExpandLevel(TreeViewer.ALL_LEVELS);
				viewer.getTree().setLayoutData(
						new TableWrapData(TableWrapData.FILL_GRAB,
								TableWrapData.FILL_GRAB));

				viewer.setInput(model);

				fillViewerMenu(viewer);

				/*
				 * For a view to be a selection provider, it must register
				 * itself as a selection provider with the view site.
				 * 
				 * Moreover, each of the objects contained in the viewer should
				 * be adaptable.
				 */
				getSite().setSelectionProvider(viewer);

				super.createFormContent(managedForm);
			}
		};

		try {
			int index = addPage(page);
			setPageText(index, Messages.TrainingPageEditor_10);
			setPageImage(index,
					Activator.getDefault().getImage(Activator.IMAGE_XML));
		} catch (PartInitException e) {
			LogManager.getLogger().logError(e);
		}
	}

	/**
	 * Fill the viewer menu
	 */
	private void fillViewerMenu(final TreeViewer viewer) {

		MenuManager menuManager = new MenuManager();

		addCAction = new AddAction(true);
		remCAction = new RemAction(true);
		addEAction = new AddAction(false);
		remEAction = new RemAction(false);

		addCAction.setEnabled(false);
		remCAction.setEnabled(false);
		addEAction.setEnabled(false);
		remEAction.setEnabled(false);

		menuManager.add(new Separator(Messages.TrainingPageEditor_11));
		menuManager.add(addCAction);
		menuManager.add(remCAction);
		menuManager.add(new Separator(Messages.TrainingPageEditor_12));
		menuManager.add(addEAction);
		menuManager.add(remEAction);

		Menu old = viewer.getTree().getMenu();
		if (old != null)
			old.dispose();

		Menu menu = menuManager.createContextMenu(viewer.getTree());
		viewer.getTree().setMenu(menu);

		menuManager.addMenuListener(new IMenuListener() {

			@Override
			public void menuAboutToShow(IMenuManager manager) {
				IStructuredSelection sel = (IStructuredSelection) viewer
						.getSelection();

				if (sel.getFirstElement() instanceof ModelContainer) {
					addCAction.setEnabled(true);
					remCAction.setEnabled(true);
					addEAction.setEnabled(true);
					remEAction.setEnabled(false);
				} else if (sel.getFirstElement() instanceof ModelElement) {
					addCAction.setEnabled(false);
					remCAction.setEnabled(false);
					addEAction.setEnabled(false);
					remEAction.setEnabled(true);
				}
			}
		});
		menuManager.updateAll(true);
	}

	/**
	 * Remove the resource change listener
	 */
	public void dispose() {
		ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
		super.dispose();
	}

	/**
	 * Saves the multi-page editor's document.
	 */
	public void doSave(IProgressMonitor monitor) {
		monitor.beginTask(Messages.TrainingPageEditor_13, IProgressMonitor.UNKNOWN);
		FileEditorInput input = (FileEditorInput) getEditorInput();
		IFile file = input.getFile();

		Document domModel = ModelProvider.buildDOM(model);
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		XMLOutputter outputter = new XMLOutputter();
		try {
			outputter.setFormat(Format.getPrettyFormat());
			outputter.output(domModel, out);

			if (!file.exists())
				file.create(null, true, monitor);

			file.setContents(new ByteArrayInputStream(out.toByteArray()), true,
					false, monitor);
			dirty = false;
			changed = false;
			firePropertyChange(PROP_DIRTY);

		} catch (IOException e) {
			LogManager.getLogger().logError(e);
		} catch (CoreException e) {
			LogManager.getLogger().logError(e);
		}

		monitor.done();
	}

	/**
	 * Saves the multi-page editor's document as another file..
	 */
	public void doSaveAs() {
		// Show a SaveAs dialog
		Shell shell = getSite().getWorkbenchWindow().getShell();
		SaveAsDialog dialog = new SaveAsDialog(shell);
		dialog.setOriginalFile(((IFileEditorInput) getEditorInput()).getFile());
		dialog.open();

		IPath path = dialog.getResult();
		if (path != null) {
			// try to save the editor's contents under a different file name
			final IFile file = ResourcesPlugin.getWorkspace().getRoot()
					.getFile(path);
			try {
				new ProgressMonitorDialog(shell).run(false, false, // not
																	// cancellable
						new WorkspaceModifyOperation() { // run this operation
							public void execute(final IProgressMonitor monitor) {
								doSave(monitor);
							}
						});
				// set input to the new file
				setInput(new FileEditorInput(file));
			} catch (InterruptedException e) {
				// should not happen, since the monitor dialog is not
				// cancellable
				LogManager.getLogger().logError(e);
			} catch (InvocationTargetException e) {
				LogManager.getLogger().logError(e);
			}
		}
	}

	/*
	 * (non-Javadoc) Method declared on IEditorPart.
	 */
	public boolean isSaveAsAllowed() {
		return true;
	}

	public TreeViewer getViewer() {
		return viewer;
	}

	public ModelContainer getModel() {
		return model;
	}

	/**
	 * Return dirty state
	 */
	@Override
	public boolean isDirty() {
		return dirty || super.isDirty();
	}

	/**
	 * Handle dirty state
	 */
	@Override
	protected void handlePropertyChange(int propertyId) {
		if (propertyId == PROP_DIRTY)
			dirty = isDirty();
		super.handlePropertyChange(propertyId);
	}

	/**
	 * Update dirty state
	 */
	public void treeModified() {
		dirty = true;
		super.firePropertyChange(PROP_DIRTY);
	}

	/**
	 * If something has changed, request to update the model
	 */
	@Override
	public void setFocus() {
		if (changed) {

			try {
				if (MessageDialog
						.openConfirm(Display.getDefault().getActiveShell(),
								Messages.TrainingPageEditor_14,
								Messages.TrainingPageEditor_15)) {
					model = ModelProvider.update(getEditorInput());
					viewer.setInput(model);
					viewer.refresh();
				}

			} catch (SAXException e) {
				LogManager.getLogger().logError(e);
			} catch (IOException e) {
				LogManager.getLogger().logError(e);
			} catch (CoreException e) {
				LogManager.getLogger().logError(e);
			}
			changed = false;
		}
		super.setFocus();
	}

	/**
	 * Notifies this listener that some resource changes are happening, or have
	 * already happened.
	 */
	@Override
	public void resourceChanged(IResourceChangeEvent event) {
		try {
			FileEditorInput input = (FileEditorInput) getEditorInput();
			IResource res = event.getResource();

			LogManager.getLogger().logInfo(
					"Event: " + event.getType() + " " + event.getResource()); //$NON-NLS-1$ //$NON-NLS-2$

			switch (event.getType()) {
			case IResourceChangeEvent.PRE_DELETE:
				if (res != null && res.equals(input.getFile().getProject()))
					deleted = true;
				break;
			case IResourceChangeEvent.PRE_CLOSE:
				System.out.println(res);
				if (res != null && res.equals(input.getFile().getProject())) {
					if (!deleted) {
						Display.getDefault().syncExec(new Runnable() {
							public void run() {
								getSite().getPage().closeEditor(
										TrainingPageEditor.this, isDirty());
							}
						});
					}

				}
				break;
			case IResourceChangeEvent.POST_CHANGE:
				if (event.getDelta() != null)
					event.getDelta().accept(new DeltaPrinter());
				break;
			}
		} catch (CoreException e) {
			LogManager.getLogger().logError(e);
		}
	}

	/**
	 * Used to contribute to the properties view
	 */
	@Override
	public String getContributorId() {
		return getSite().getId();
	}

	@SuppressWarnings("rawtypes")
	@Override
	public Object getAdapter(Class adapter) {
		if (adapter == IPropertySheetPage.class)
			return new TabbedPropertySheetPage(this);
		return super.getAdapter(adapter);
	}

}
